#ifndef WALL_H
#define WALL_H

#include "string"

#include "ngl/Transformation.h"
#include "ngl/TransformStack.h"

#include "ShaderManager.h"
#include "Boid.h"

/// @file Wall.h
/// @brief represents the wall boundaries of the flocking system
/// @author Perseedoss Rajiv
/// @version 1.0
/// @date 29.03.11
/// Revision History :
/// Initial Version 29.03.11
/// @class Wall
/// @brief defines the main bounding box/field area of the simulation

class Wall
{
public:
    /// @brief ctor
    /// @param[in] _radius the radius of the wall boundary
    /// @param[in] _enableWall flag to determine whether the wall is enabled
    /// @param[in] _hardWall flag to determine the mode of limiting (soft/hard)
    /// @param[in] _weight the weight of influence of the soft wall limiting
    Wall(
            const float _radius = 0,
            const bool _enableWall = false,
            const bool _hardWall = true,
            const float _weight = 0.1
        );

    /// @brief dtor
    inline ~Wall() { std::cout << "Wall deleted\n" << std::endl; }

    /// @brief creates vbo for the wall planes
    void loadGeometry();

    /// @brief draws the wall
    /// @param[in] _txStack the transform stack, used for pushing and popping opengl states
    /// @param[in,out] io_sman the shader manager, used for shading/rendering of the objects
    /// @param[in] _shader the shader used for drawing
    void draw
            (
                ngl::TransformStack _txStack,
                ShaderManager* io_sman,
                const std::string _shader
            );

    /// @brief checks if a boid is within the wall bounds
    /// @param[in] _boid the boid that is checked upon
    /// @param[out] o_deltaPosition a change vector that is required to bring the boid back within the bounds
    bool isWithinBounds
            (
                const Boid* _boid,
                ngl::Vector &o_deltaPosition
            ) const;

    /// @brief returns the state of wall enablement
    inline bool getEnableWall() const { return m_enableWall; }

    /// @brief set the flag to determine if the wall is enabled
    /// @param[in] _state the state of enablement of the wall
    inline void setEnableWall(const bool _state) { m_enableWall = _state; }

    /// @brief returns whether hard wall limiting is enabled
    inline bool getHardWall() const { return m_hardWall; }

    /// @brief set the flag to determine which mode of wall limiting is used (hard/soft)
    /// @param[in] _state the type of limiting used (true = hard, false = soft)
    inline void setHardWall(const bool _state) { m_hardWall = _state; }

    /// @brief set the weight of the soft wall limiting
    /// @param[in] _w the weight of influence of the limiting
    inline void setSoftLimitWeight(const float _w) { m_softLimitWeight = _w; }

    /// @brief returns the weight of the soft wall limiting
    inline float getSoftLimitWeight() const { return m_softLimitWeight; }

    /// @brief set the boundaries of the wall
    /// @param[in] _bound the boundary limits to be set to the wall
    void setBounds(const ngl::Vector _bound);

    /// @brief returns the boundaries of the wall
    ngl::Vector getBounds() const;


private:
    /// @brief the minimum x bound limit
    float xMin;

    /// @brief the maximum x bound limit
    float xMax;

    /// @brief the minimum y bound limit
    float yMin;

    /// @brief the maximum y bound limit
    float yMax;

    /// @brief the minimum z bound limit
    float zMin;

    /// @brief the maximum z bound limit
    float zMax;

    /// @brief flag to determine the enablement of the wall
    bool m_enableWall;

    /// @brief flag to toggle between hard/soft wall limiting
    bool m_hardWall;

    /// @brief the weight of the soft wall limiting
    float m_softLimitWeight;


private:
    /// @brief initialise the boundaries of the wall
    /// @param[in] _radius the radius of the wall boundary
    void initialiseBounds(const float _radius);

    /// @brief initialise the boundaries of the wall, overloaded to take in limits
    /// @param[in] _xLimits the x limits of the wall boundary, spanning from -xlimits/2 to +xlimits/2
    /// @param[in] _yLimits the y limits of the wall boundary, spanning from -ylimits/2 to +ylimits/2
    /// @param[in] _zLimits the z limits of the wall boundary, spanning from -zlimits/2 to +zlimits/2
    void initialiseBounds
                        (
                            const float _xLimits,
                            const float _yLimits,
                            const float _zLimits
                        );

};

#endif // WALL_H
